#!/usr/bin/env bash
# 脚本说明：递归格式化当前目录及子目录中的所有常见 C++ 文件，并输出统计信息。
# 参数：
#   1) 可选，第一个参数为 .clang-format 配置文件路径；
#      若未提供，则自动在当前 Git 仓库的根目录中查找 .clang-format；
#      若仍未找到，则中止并返回错误。
# 返回值：
#   0：成功执行；非 0：发生错误（如未找到 clang-format 或配置文件、Git 仓库不存在等）。
# 可能错误：
#   - 未安装或无法找到 clang-format
#   - 未提供配置路径且无法定位 Git 仓库根或仓库根没有 .clang-format
#   - 指定的配置文件不存在或不可读

set -u -o pipefail

# 切换到脚本所在目录，保证文件搜索的相对路径一致
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$script_dir"

# 计时开始
start_ts="$(date +%s)"

# 检查 clang-format 可用性
if ! command -v clang-format >/dev/null 2>&1; then
  echo "错误：未找到 clang-format，请先安装。" >&2
  exit 1
fi

# 解析配置文件路径参数
config_path=""
if [[ $# -ge 1 && -n "${1:-}" ]]; then
  config_path="$1"
  if [[ ! -f "$config_path" ]]; then
    echo "错误：指定的 .clang-format 文件不存在：$config_path" >&2
    exit 2
  fi
else
  # 未提供参数时，尝试从 Git 仓库根查找 .clang-format
  repo_root=""
  if command -v git >/dev/null 2>&1; then
    repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
  fi
  if [[ -n "${repo_root:-}" && -f "$repo_root/.clang-format" ]]; then
    config_path="$repo_root/.clang-format"
  else
    echo "错误：未提供 .clang-format 路径，且在 Git 仓库根目录未找到 .clang-format，任务中止。" >&2
    exit 2
  fi
fi

# 读取配置内容，作为 -style 的 YAML 字符串传递给 clang-format
# style_content="$(cat "$config_path")"
# if [[ -z "$style_content" ]]; then
#   echo "错误：读取 .clang-format 失败或内容为空：$config_path" >&2
#   exit 2
# fi
#
# echo "使用的配置文件：$config_path"
#
# 先验证样式内容是否能被当前 clang-format 接受
# style_valid=1
# if ! clang-format -style="$style_content" -dump-config >/dev/null 2>&1; then
#   style_valid=0
#   echo "警告：无法解析提供的 .clang-format 内容，将回退为 --style=file。" >&2
# fi

# 使用 --style=file + --assume-filename，强制从指定配置目录加载 .clang-format
config_dir="$(dirname "$config_path")"
echo "使用的配置文件：$config_path"
echo "配置目录：$config_dir"

# 设置搜索根目录：优先使用 Git 仓库根，其次使用脚本所在目录
search_root="$script_dir"
if command -v git >/dev/null 2>&1; then
  git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
  if [[ -n "${git_root:-}" ]]; then
    search_root="$git_root"
  fi
fi

# 需要处理的常见 C++ 文件后缀
extensions=(
  "*.h" "*.hh" "*.hpp" "*.hxx" "*.inl" "*.ixx"
  "*.cxx" "*.cc" "*.cpp" "*.ipp" "*.tpp" "*.txx"
)

# 构造 find 表达式：跳过常见构建目录，仅匹配上面的扩展名
find_expr=( "$search_root" 
  -type d \( -name "contrib" -o -name ".git" -o -name "build" -o -name "cmake-build-*" \) -prune -o
  -type f \(
)
for i in "${!extensions[@]}"; do
  find_expr+=( -name "${extensions[$i]}" )
  if [[ $i -lt $((${#extensions[@]} - 1)) ]]; then
    find_expr+=( -o )
  fi
done
find_expr+=( \) -print0 )

# 收集文件列表（处理含空格/特殊字符的文件名）
mapfile -d '' files < <(find "${find_expr[@]}")

# 统计计数
total_count=0
formatted_count=0
unchanged_count=0
error_count=0

# 逐文件格式化并统计
for f in "${files[@]}"; do
  total_count=$((total_count + 1))
  # 记录格式化前后哈希以判断是否有改动
  pre_hash="$(sha256sum "$f" | cut -d ' ' -f1)"

  # 使用 --style=file，并通过 --assume-filename 指定一个位于配置目录的伪文件名，确保读取该目录的 .clang-format。
  # 同时保持语言识别一致：伪文件的扩展名与实际文件一致。
  ext="${f##*.}"
  assume_file="$config_dir/dummy.$ext"
  if ! output=$(clang-format -i --style=file --assume-filename="$assume_file" "$f" 2>&1); then
    error_count=$((error_count + 1))
    echo "格式化失败：$f" >&2
    printf '%s\n' "$output" | sed -n '1,3p' >&2
    continue
  fi

  post_hash="$(sha256sum "$f" | cut -d ' ' -f1)"
  if [[ "$pre_hash" != "$post_hash" ]]; then
    formatted_count=$((formatted_count + 1))
  else
    unchanged_count=$((unchanged_count + 1))
  fi

done

end_ts="$(date +%s)"
elapsed=$((end_ts - start_ts))

# 输出统计信息
echo "格式化统计："
echo "  总文件数：$total_count"
echo "  成功格式化（有改动）：$formatted_count"
echo "  无需变更（无改动）：$unchanged_count"
echo "  失败：$error_count"
echo "  用时：${elapsed} 秒"